﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.Linq;
using System.Linq.Expressions;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Security;
using System.Security;
using System.Security.Principal;
using System.Security.Cryptography;
using System.Text;
using System.IO;

// CommonLibrary.NET Services.
using ComLib.Account;
using ComLib.Logging;
using ComLib.Data;
using ComLib.Caching;
using ComLib.Environments;
using ComLib.Configuration;
using ComLib.Cryptography;
using ComLib.Authentication;
using ComLib.EmailSupport;
using ComLib.Notifications;
using ComLib.Entities;
using ComLib.LocationSupport;
using ComLib.Extensions;
using ComLib.BootStrapSupport;
using ComLib.Queue;
using ComLib.Scheduling;
using ComLib.MapperSupport;

// CommonLibrary.NET Web Modules
using ComLib.Web.Modules;
using ComLib.Web.Modules.Pages;
using ComLib.Web.Modules.Events;
using ComLib.Web.Modules.Links;
using ComLib.Web.Modules.Posts;
using ComLib.Web.Modules.Parts;
using ComLib.Web.Modules.Tags;
using ComLib.Web.Modules.Rolls;
using ComLib.Web.Modules.Profiles;
using ComLib.Web.Modules.Feedbacks;
using ComLib.Web.Modules.MenuEntrys;
using ComLib.Web.Modules.Widgets;
using ComLib.Web.Modules.Helpers;
using ComLib.Web.Modules.Services;
using ComLib.Web.Modules.Settings;


// CommonLibrary.NET ASP.NET MVC 2 Specific code.
using ComLib.Web.Lib.Controllers;
using ComLib.Web.Lib.Helpers;
using ComLib.CMS.Areas.Admin;


namespace ComLib.CMS
{
    /// <summary>
    /// Note: For instructions on enabling IIS6 or IIS7 classic mode, 
    /// visit http://go.microsoft.com/?LinkId=9394801    
    /// </summary>
    public class MvcApplication : System.Web.HttpApplication
    {
        /// <summary>
        /// Configure the application and then register all the routes.
        /// This is because after the configuration, the dynamic html pages will be processed in the routes.
        /// </summary>
        protected void Application_Start()
        {
            
            var app = new AppConfigurator();
            Configure(app);
        }


        /// <summary>
        /// This configures the application by initializing all the various services.
        /// 1. Set environment. There is dev and dev2 which correspond to the config files in \config directory.
        /// 2. Config files
        /// 3. Email notifications                
        /// 4. Repository configuration
        /// 5. Account Membership.
        /// 6. Entity/Repository initialization            
        /// 7. Authentication
        /// 8. Logging to In-Memory Database. Can be replaced w/ real db.            
        /// 9. Configure dashboard w/ the models.                            
        /// </summary>
        public void Configure(AppConfigurator app)
        {            
            // Initialize all the various services for the application including:
            string _ENVIRONMENT_ = "dev";
            string configDir = Server.MapPath(@"Config/Env");
            bool useRealData = false;            
            var bootup = new BootStrapper();
            Application["start_time"] = DateTime.Now;
            Application["bootstrap"] = bootup;            
            
            bootup.OnStart("Env", false, (ctx) => Envs.Set(_ENVIRONMENT_, "prod,qa,dev2:dev,dev", "prod.config,qa.config,dev2.config,dev.config", true, true));
            bootup.OnStart("Conf", false, (ctx) => Config.Init(Configs.LoadFiles(configDir, Env.RefPath)));
            bootup.OnStart("Db", false, (ctx) => RepositoryFactory.Add(Config.Get<string>("Database", "connectstr")));
            bootup.OnStart("FormsAuth", false, (ctx) => FormsAuthentication.Initialize());
            bootup.OnStart("Repos", false, (ctx) => app.ConfigureRepositories(useRealData, ctx.Bag));
            bootup.OnStart("Logs", false, (ctx) => Logger.Default.Replace(new LogDatabase("commons.mvc", "db", ctx.Bag["logrepo"] as IRepository<LogEventEntity>, LogLevel.Debug)));
            bootup.OnStart("Queues", false, (ctx) => app.ConfigureQueueProcessing());
            bootup.OnStart("Schedule", false, (ctx) => app.ConfigureSchedules());
            bootup.OnStart("Auth1", false, (ctx) => Auth.Init(new AuthWin("Admin", new UserPrincipal(1, "admin", "Admin", "custom", true))));
            bootup.OnStart("LoadData", true, (ctx) => app.LoadData());
            bootup.OnStart("Dashboard", false, (ctx) => app.ConfigureDashBoard());
            bootup.OnStart("Mails", false, (ctx) => app.ConfigureNotifications());
            bootup.OnStart("Configs", false, (ctx) => app.ConfigureSettings(ctx.Bag));
            bootup.OnStart("MVC_Areas", false, (ctx) => AreaRegistration.RegisterAllAreas());
            bootup.OnStart("MVC_Routes", false, (ctx) => app.ConfigureRoutes());
            bootup.OnStart("Auth2", false, (ctx) => Auth.Init(new AuthWeb("Admin")));
            //bootup.OnStart("FakeData", false, (ctx) => { if (!useRealData) DataBuilder.IncreaseFakeDataContent(); });
            bootup.StartUp(new AppContext());
            DataBuilder.IncreaseFakeDataContent();
            // Nice themes:
            // http://blog.sb2.fr/post/2008/12/19/Reading-and-Writing-RSS-Feeds-Using-SystemServiceModelSyndication.aspx
            // http://www.learningjquery.com/2006/09/basic-show-and-hide
        }


        /// <summary>
        /// Authenticate the request.
        /// Using Custom Forms Authentication since I'm not using the "RolesProvider".
        /// Roles are manually stored / encrypted in a cookie in <see cref="FormsAuthenticationService.SignIn"/>
        /// This takes out the roles from the cookie and rebuilds the Principal w/ the decrypted roles.
        /// http://msdn.microsoft.com/en-us/library/aa302397.aspx
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        protected void Application_AuthenticateRequest(Object sender, EventArgs e)
        {
            SecurityHelper.RebuildUserFromCookies();
        }


        /// <summary>
        /// Currently used for re-writing static pages urls.
        /// E.g. Instead of adding to routes "news", "aboutus". it's handled here a bit more dynamically.
        /// Otherwise, we would have to register each static page in the route table.
        /// Need to store the original url becuase an asp.net bug always puts in the
        /// physical url as the postback url for forms.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void Application_BeginRequest(object sender, EventArgs e)
        {            
            string filename = Path.GetFileName(HttpContext.Current.Request.RawUrl);
            if (!string.IsNullOrEmpty(filename))
            {
                if (MenuEntry.Lookup.ContainsKey(filename) || Page.Lookup.ContainsKey(filename))
                {
                    Context.Items["originalurl"] = HttpContext.Current.Request.RawUrl;
                    string url = "/page/showbyname/" + filename;
                    HttpContext.Current.RewritePath(url);
                }
            }
        }
    }



    public class AppConfigurator
    {
        /// <summary>
        /// Load data from the csv file into the appropriate entity repos.
        /// This does a conditional load, meaning the data from the csv/ini files are ONLY
        /// saved into the system if there are no existing records w/ matching fields.
        /// E.g. In the case of the Roll model, it's only saved to the system if there is 
        /// NO record with matching values for BloggerName and Url fields.
        /// </summary>
        public void LoadData()
        {
            var server = HttpContext.Current.Server;                        
            User.Create(Mapper.MapFile<User>(server.MapPath("/config/data/users.csv")), u => u.UserName);
            Page.Create(Mapper.MapFile<Page>(server.MapPath("/config/data/pages.csv")), p => p.Title, p => p.Slug);
            Part.Create(Mapper.MapFile<Part>(server.MapPath("/config/data/parts.csv")), r => r.Title);
            Roll.Create(Mapper.MapFile<Roll>(server.MapPath("/config/data/rolls.csv")), r => r.BloggerName, r => r.Url);
            Link.Create(Mapper.MapFile<Link>(server.MapPath("/config/data/links.csv")), l => l.Name, l => l.Group, l => l.Url);
            Profile.Create(Mapper.MapFile<Profile>(server.MapPath("/config/data/profiles.csv")), true, u => u.UserName);
            Widget.Create(Mapper.MapFile<Widget>(server.MapPath("/config/data/widgetdefs.csv")), w => w.Title);
            WidgetInstance.Create(Mapper.MapFile<WidgetInstance>(server.MapPath("/config/data/widgetInstances.csv")), true);
            MenuEntry.Create(Page.FrontPagesAsMenuEntrys().AddRange(Mapper.MapFile<MenuEntry>(server.MapPath("/config/data/menu.csv"))), m => m.Name);
            Post.Create(Mapper.MapFile<Post>(server.MapPath("/config/data/posts.ini")), p => p.Title, p => p.CreateUser);
            Event.Create(Mapper.MapFile<Event>(server.MapPath("/config/data/events.ini")), e => e.Title, e => e.CreateUser);
            if(LogEventEntity.Count() == 0)
                ((int)40).Times(ndx => Logger.Default.Log(LogLevel.Info, "Testing logging"));
        }


        /// <summary>
        /// Configures the routest for MVC.
        /// NOTE: The order of these may need to be changed to slightly.
        /// </summary>
        public void ConfigureRoutes()
        {
            RouteCollection routes = RouteTable.Routes;
            routes.IgnoreRoute("{resource}.axd/{*pathInfo}");
            routes.MapRoute("Pages",    "page/showbyname/{pagename}", new { controller = "Page", action = "ShowByName", pagename = "" });
            routes.MapRoute("Profiles", "profile/editbyname/{username}", new { controller = "Profile", action = "EditByName", username = "" });
            routes.MapRoute("Feedback", "feedback/", new { controller = "Feedback", action = "Create" });
            routes.MapRoute("Account",  "account/{action}", new { controller = "Account", action = "" });
            routes.MapRoute("Tag",      "{controller}/tags/{tag}", new { controller = "Home", action = "Tags", tag = "" });
            routes.MapRoute("Archive",  "{controller}/archives/{year}/{month}", new { controller = "Home", action = "Archives", year = "", month = "" });
            routes.MapRoute("Index",    "{controller}/index/{page}", new { controller = "Home", action = "Index", page = "" });
            routes.MapRoute("IndexM",   "{controller}/indexmanage/{page}", new { controller = "Home", action = "IndexManage", page = "" });
            routes.MapRoute("Manage",   "{controller}/manage/{page}", new { controller = "Home", action = "Manage", page = "" });
            routes.MapRoute("Seo",      "{controller}/show/{title}", new { controller = "Home", action = "Show", title = "" });
            routes.MapRoute("Default",  "{controller}/{action}/{id}", new { controller = "Home", action = "Index", id = "" });
        }


        /// <summary>
        /// Saves all the data from the internal system to csv files.
        /// </summary>
        public void SaveData()
        {
            IErrors errors = new Errors();
            List<string> excludeProps = new List<string>() { "Settings", "Errors", "IsValid" };
            Mapper.MapToCsv<User>(User.GetAll(), "config/data/users.csv", errors, excludeProps);
            Mapper.MapToCsv<Page>(Page.GetAll(), "config/data/pages.csv", errors, excludeProps);
            Mapper.MapToCsv<Part>(Part.GetAll(), "config/data/parts.csv", errors, excludeProps);
            Mapper.MapToCsv<Roll>(Roll.GetAll(), "config/data/rolls.csv", errors, excludeProps);
            Mapper.MapToCsv<Link>(Link.GetAll(), "config/data/links.csv", errors, excludeProps);
            Mapper.MapToCsv<Widget>(Widget.GetAll(), "config/data/widgets.csv", errors, excludeProps);
            Mapper.MapToCsv<MenuEntry>(MenuEntry.GetAll(), "config/data/menuentrys.csv", errors, excludeProps);
            Mapper.MapToCsv<WidgetInstance>(WidgetInstance.GetAll(), "config/data/widgetinstances.csv", errors, excludeProps);
            Mapper.MapToCsv<Profile>(Profile.GetAll(), "config/data/profiles.csv", errors, excludeProps);
        }


        /// <summary>
        /// Configures the dashboard with the Models that can be managed( Add,Edit,Index,Delete).
        /// Also, add links for configuration settings for each model.
        /// </summary>
        public void ConfigureDashBoard()
        {
            //    By default, the index, manage urls do Get(pageindex, pagesize) and FindByUser(user, page, pagesize)
            //    respectively.
            //    By overriding the defaults here in "index" and "indexmanage", the difference here is 
            //    that the "index" url will not show the "edit,delete" links where as indexmanage will.
            Dashboard.Models.Add(new ModelEntry(typeof(Event)),
                                 new ModelEntry(typeof(Post)),
                                 new ModelEntry(typeof(Page),           null,   "index", "indexmanage"),
                                 new ModelEntry(typeof(MenuEntry),      "Menu", "index", "indexmanage"),
                                 new ModelEntry(typeof(Part),           null,   "index", "indexmanage"),
                                 new ModelEntry(typeof(Roll),           null,   "index", "indexmanage"),
                                 new ModelEntry(typeof(Link),           null,   "index", "indexmanage"),
                                 new ModelEntry(typeof(Feedback),       null,   "index", "indexmanage"),
                                 new ModelEntry(typeof(WidgetInstance), "Widget", "index", "indexmanage"));

            // Settings by category.
            Dashboard.Configs.Add("Post", Post.Settings);
            Dashboard.Configs.Add("General", SiteSettings.Instance.Site);
        }


        /// <summary>
        /// This configures processing queues.
        /// </summary>
        public void ConfigureQueueProcessing()
        {
            // Applying tags is queued up and handled. Instead of applying them for each post in realtime.
            Queues.AddProcessorFor<TagsEntry>(15, items => TagHelper.ProcessQueue(items));
        }


        /// <summary>
        /// This handles the configuration settings for each model.
        /// They can be either saved/loaded from the datastore.
        /// </summary>
        /// <param name="ctxBag"></param>
        public void ConfigureSettings(IDictionary<string, object> ctxBag)
        {
            int count = ConfigItem.Count();
            IRepository<ConfigItem> repo = ctxBag["configRepo"] as IRepository<ConfigItem>;
            SiteSettings.Instance.Site.SetRepository(null, repo);
            SiteSettings.Instance.Posts.SetRepository(null, repo);
            ControllerFactoryHelper.Init(() => SiteSettings.Instance.Site);
            SiteSettings.Instance.Site.Author = "kishore reddy";
            // Save settings.
            //if (count == 0)
            //{
            //    SiteSettings.Instance.Posts.Save();
            //    SiteSettings.Instance.Site.Save();
            //}
            //else if (Config.Get<bool>("App", "loadsettings"))
            //{
            //    SiteSettings.Instance.Site.Load();
            //    SiteSettings.Instance.Posts.Load();
            //}
        }


        /// <summary>
        /// Configures the task scheduler.
        /// </summary>
        public void ConfigureSchedules()
        {
            Scheduler.Schedule("apply-tags", new Trigger().Every(((int)20).Seconds()), true, () => Queues.Process<TagsEntry>());
            //Scheduler.Schedule("send-emails", new Trigger().Every(((int)40).Seconds()), true, () => Notifier.Process());
        }


        /// <summary>
        /// Configure the notification emailer.
        /// </summary>
        public void ConfigureNotifications()
        {
            Notifier.Init(new EmailService(Config.Current, "EmailServiceSettings"), new NotificationSettings());
            Notifier.Settings["website.name"] = "KnowledgeDrink.com";
            Notifier.Settings["website.url"] = "http://www.KnowledgeDrink.com";
            Notifier.Settings["website.urls.contactus"] = "http://www.KnowledgeDrink.com/contactus";
            Notifier.Settings.EnableNotifications = false;
        }


        /// <summary>
        /// Configure the repositories.
        /// Use In-Memory repositories if faking the storage.
        /// </summary>
        /// <param name="useFake"></param>
        /// <param name="args"></param>
        public IDictionary<string, object> ConfigureRepositories(bool useRealData, IDictionary<string, object> repos)
        {
            IRepository<LogEventEntity> logrepo = null;
            IRepository<ConfigItem> configRepo = null;
            UserSettings settings = new UserSettings();
            settings.UserNameRegEx = @"[a-zA-Z1-9\._]{3,15}";
            settings.PasswordRegEx = "[a-zA-Z1-9]{5,15}";

            if (useRealData)
            {
                // Real Database setup.
                // 1. The Repository<T> base class will automatically default the IDatabase to Database class.
                // 2. The RepositoryFactory will configure the connection, since the last parameter is "true".                
                string con = Config.Get<string>("Database", "connectstr");
                logrepo = new LogRepository(con);
                configRepo = new ConfigItemRepository();
                LogEventEntity.Init(new EntityService<LogEventEntity>(logrepo, null, null), true);

                // NOTE: Can use an IoC Container here.
                // e.g. () => IoC.Get<UserService>(), () => IoC.Get<UserRepository>() ) etc..
                ComLib.Account.User.Init(() => new UserService(), () => new UserRepository(), () => new UserValidator(), settings, true, null);
                WidgetInstance.Init(() => new WidgetInstanceRepository(), true);
                MenuEntry.Init(() => new MenuEntryRepository(), true);
                ConfigItem.Init(() => configRepo, true);
                Widget.Init(() => new WidgetRepository(), true);
                Feedback.Init(() => new FeedbackRepository(), true);
                Profile.Init(() => new ProfileRepository(), true);
                Event.Init(() => new EventRepository(), true);
                Link.Init(() => new LinkRepository(), true);
                Post.Init(() => new PostRepository(), Post.Settings, true);
                Page.Init(() => new PageRepository(), true);
                Part.Init(() => new PartRepository(), true);
                Roll.Init(() => new RollRepository(), true);
                Tag.Init(() => new TagRepository(), true);
            }
            else
            {
                // In-Memory repositories.
                logrepo = new RepositoryInMemory<LogEventEntity>("Id,CreateDate,UserName,LogLevel");
                configRepo = new RepositoryInMemory<ConfigItem>();                
                LogEventEntity.Init(new EntityService<LogEventEntity>(logrepo, null, new EntitySettings<LogEventEntity>()));
                IRepository<Profile> profileRepo = new RepositoryInMemory<Profile>();
                IRepository<WidgetInstance> widgetInstanceRepo = new RepositoryInMemory<WidgetInstance>();
                IRepository<Widget> widgetRepo = new RepositoryInMemory<Widget>();
                IRepository<Event> eventRepo = new RepositoryInMemory<Event>("Id,CreateUser,Title,StartDate");
                IRepository<MenuEntry> menuRepo = new RepositoryInMemory<MenuEntry>();
                IRepository<Feedback> feedbackRepo = new RepositoryInMemory<Feedback>();
                IRepository<Link> linkRepo = new RepositoryInMemory<Link>();
                IRepository<Page> pageRepo = new RepositoryInMemory<Page>();
                IRepository<Post> blogRepo = new RepositoryInMemory<Post>();
                IRepository<Part> partRepo = new RepositoryInMemory<Part>();
                IRepository<Roll> rollRepo = new RepositoryInMemory<Roll>();
                IRepository<Tag> tagRepo = new RepositoryInMemory<Tag>();
                IRepository<User> userRepo = new RepositoryInMemory<User>();

                ComLib.Account.User.Init(() => new UserService(), () => userRepo, () => new UserValidator(), settings, true, null);
                WidgetInstance.Init(() => widgetInstanceRepo, true);
                Widget.Init(() => widgetRepo, true);
                Feedback.Init(() => feedbackRepo, true);
                MenuEntry.Init(() => menuRepo, true);
                ConfigItem.Init(() => configRepo, true);
                Profile.Init(() => profileRepo, true);
                Event.Init(() => eventRepo, true);
                Link.Init(() => linkRepo, true);
                Post.Init(() => blogRepo, true);
                Page.Init(() => pageRepo, true);
                Part.Init(() => partRepo, true);
                Roll.Init(() => rollRepo, true);
                Tag.Init(() => tagRepo, true);

                // Temporarily set the auth provider so we the posts can have "CreateUser" populated.
                Auth.Init(new AuthWin("admin", new UserPrincipal(1, "kishore", "admin", "", true)));
            }
            repos["logrepo"] = logrepo;
            repos["configRepo"] = configRepo;
            configRepo.TableName = "configs";
            logrepo.TableName = "logs";
            return repos;
        }
    }



    /// <summary>
    /// Used to create test data for the In-Memory Repositories used by this sample application.
    /// </summary>
    public class DataBuilder
    {

        public static void IncreaseFakeDataContent()
        {
            Post.GetAll().ForEach(entity =>
            {
                entity.Description = entity.Title.Times(20).Truncate(200);
                if (entity.Content.Length < 500)
                    entity.Content = entity.Content.Times(20).Truncate(500);
                entity.Update();
            });
            Page.GetAll().ForEach(entity =>
            {
                entity.Description = entity.Title.Times(20).Truncate(200);
                if (entity.Content.Length < 500)
                    entity.Content = entity.Content.Times(20).Truncate(500);
                entity.Update();
            });
            Event.GetAll().ForEach(entity =>
            {
                entity.Description = entity.Title.Times(20).Truncate(200);
                if (entity.Content.Length < 500)
                    entity.Content = entity.Content.Times(20).Truncate(500);
                entity.Update();
            });
        }        
    }
}